{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f5d674e9-e6bd-40f3-b646-d874cc31d439",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating data folder...\n",
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 170M/170M [00:00<00:00, 288MB/s]\n",
      "Extracting tar.gz file...\n",
      "Successfully downloaded / unzipped to ./datasets-cifar10-bin\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'./datasets-cifar10-bin'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n",
    "\n",
    "download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "322372a3-8d03-441d-9d20-d53b5ad62f22",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore as ms\n",
    "import mindspore.dataset as ds\n",
    "import mindspore.dataset.vision as vision\n",
    "import mindspore.dataset.transforms as transforms\n",
    "from mindspore import dtype as mstype\n",
    "\n",
    "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\"  # 数据集根目录\n",
    "batch_size = 256  # 批量大小\n",
    "image_size = 32  # 训练图像空间大小\n",
    "workers = 4  # 并行线程个数\n",
    "num_classes = 10  # 分类数量\n",
    "\n",
    "\n",
    "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n",
    "\n",
    "    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,\n",
    "                                 usage=usage,\n",
    "                                 num_parallel_workers=workers,\n",
    "                                 shuffle=True)\n",
    "\n",
    "    trans = []\n",
    "    if usage == \"train\":\n",
    "        trans += [\n",
    "            vision.RandomCrop((32, 32), (4, 4, 4, 4)),\n",
    "            vision.RandomHorizontalFlip(prob=0.5)\n",
    "        ]\n",
    "\n",
    "    trans += [\n",
    "        vision.Resize(resize),\n",
    "        vision.Rescale(1.0 / 255.0, 0.0),\n",
    "        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n",
    "        vision.HWC2CHW()\n",
    "    ]\n",
    "\n",
    "    target_trans = transforms.TypeCast(mstype.int32)\n",
    "\n",
    "    # 数据映射操作\n",
    "    data_set = data_set.map(operations=trans,\n",
    "                            input_columns='image',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    data_set = data_set.map(operations=target_trans,\n",
    "                            input_columns='label',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    # 批量操作\n",
    "    data_set = data_set.batch(batch_size)\n",
    "\n",
    "    return data_set\n",
    "\n",
    "\n",
    "# 获取处理后的训练与测试数据集\n",
    "\n",
    "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                       usage=\"train\",\n",
    "                                       resize=image_size,\n",
    "                                       batch_size=batch_size,\n",
    "                                       workers=workers)\n",
    "step_size_train = dataset_train.get_dataset_size()\n",
    "\n",
    "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                     usage=\"test\",\n",
    "                                     resize=image_size,\n",
    "                                     batch_size=batch_size,\n",
    "                                     workers=workers)\n",
    "step_size_val = dataset_val.get_dataset_size()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "85aa3f6d-f6a9-42d0-a34d-2fde77667532",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Image shape: (256, 3, 32, 32), Label shape: (256,)\n",
      "Labels: [7 1 9 4 9 7]\n"
     ]
    },
    {
     "data": {
      "image/png": "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yVqzZg3t27eP/uu//kvE5nI5ymQy7+WywTTj8XjEX9R33323+Cumq6uLiIjWrVs32ZfJZOj+++8Xx4xEIkZF9QUXXEAvv/wyvfDCC+wY99xzD3V0dBx27tV0HqKpz4X9+/ezf2EwPj5OP/nJT2jZsmXU1NQ05fOuWbOGKpUK3XbbbeK/lctlmNbMUCKRCBHJL/d3or29nTweD5srRH8143o79fX1tGLFCvrxj39Mu3fvZv9Nn5NEf92U3HPPPXT55ZfT1VdfTf/93//9Hu7igwd+GXgHli5dSldffTXdc889lEwmaeXKlfTyyy/T/fffT5dccgl9+MMfftdjfPvb36Z169bRhRdeSO3t7TQ4OEg/+MEPqLW1dVL09fd///f04IMP0o033khr166lM844gyqVCm3ZsoUefPDBSe8CMDO46KKL6Kc//SnF43FauHAhvfDCC/TEE09QbW0tizv33HNp9uzZ9JnPfIa++tWvksfjoR//+MdUX18vFrLly5fTf/zHf9C//Mu/UHd3NzU0NNCqVavo61//Ov385z+n888/n774xS9STU0N3X///dTb20u/+tWvjpqhz3udC/PmzaPPfOYz9Morr1BjYyP9+Mc/poGBAbr33nvf03lXrlxJN9xwA91+++20fv16Ovfcc8nn89H27dvpoYceon/7t3+jyy+//KjcI3j/WL58ORER3XTTTXTllVeSz+ejiy+++B3j4/E4XXHFFXT33XeTZVnU1dVFf/jDH4yakX//93+nM888k0466SS6/vrrqbOzk/r6+ujhhx+m9evXi3jbtulnP/sZXXLJJbRmzRp65JFHJsXbrmM6/ynDdPH2f1rydvR/1lUqldStt96qOjs7lc/nU21tbeob3/iGyufz7HPt7e3qwgsvFOd58skn1cc//nHV3Nys/H6/am5uVldddZXatm0biysWi+p73/ueWrRokQoEAqq6ulotX75c3XrrrSqVSh3dmwfHlLGxMXXNNdeouro6FY1G1Xnnnae2bNmi2tvb1dVXX81iX331VXXaaacpv9+vZs+ere68807jPy08ePCguvDCC1UsFlNExP6Z4c6dO9Xll1+uEomECgaD6tRTT1V/+MMf2HkO/dOshx56iPUfOtcrr7zC+k3z473Ohccee0wtWbJEBQIBNX/+fHHuqfzTwkPcc889avny5SoUCqlY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cTo6Ouiiiy6ixx57jE4++WQKhUKTa/bjjz9OZ555JiUSCYpGo9TT00Pf/OY32ecLhQLdfPPN1N3dTYFAgNra2uhrX/uaOI9b8E73BRwvvPXWW3TuuedSfX093XLLLVQul+nmm2+mxsZGFved73yHvvWtb9GaNWvouuuuo6GhIbr77rtpxYoV9Prrr1MikSAioqeeeorOP/98Wr58Od18881k2zbde++9tGrVKnrmmWfo1FNPZce94ooraO7cufTd736XlFLv122DGchll11G27Zto5///Od01113UV1dHRER1dfXE9Ffx96DDz5IX/jCF6iuro46OjreUzrhzTffpLPOOot8Ph9df/311NHRQTt37qTf//739J3vfMf4mZ07d9KqVauopqaGHn/88clrAkBnzZo11NnZSbfffju99tpr9KMf/YgaGhroe9/7HhERXXfddXT//ffT5ZdfTl/+8pfppZdeottvv502b95Mv/nNb9ixtm7dSldddRXdcMMN9NnPfpZ6enpo48aNdNFFF9GSJUvo29/+NgUCAdqxYwf7Q8xxHPrYxz5Gzz77LF1//fW0YMECeuutt+iuu+6ibdu20W9/+9v385EcHyiglFLqkksuUcFgUPX390/2bdq0SXk8HnXoMfX19SmPx6O+853vsM++9dZbyuv1TvY7jqPmzp2rzjvvPOU4zmRcNptVnZ2d6iMf+chk380336yISF111VXH8vbAB4w77rhDEZHq7e1l/USkbNtWGzduZP1r165VRKTWrl3L+nt7exURqXvvvXeyb8WKFSoWi7G5oJRiY/nQuB0aGlKbN29Wzc3N6pRTTlGjo6NH5f7AB49DY+baa69l/Zdeeqmqra1VSim1fv16RUTquuuuYzFf+cpXFBGpp556arKvvb1dEZF69NFHWexdd901OTbfiZ/+9KfKtm31zDPPsP4f/vCHiojUc889d1j3OJNBmoCIKpUKPfbYY3TJJZfQ7NmzJ/sXLFhA55133mT717/+NTmOQ2vWrKHh4eHJ/zU1NdHcuXNp7dq1RES0fv162r59O33qU5+ikZGRybhMJkOrV6+mdevWkeM47BpuvPHG9+dmwQeelStX0sKFCw/rs0NDQ7Ru3Tq69tpr2VwgIuM/sd2wYQOtXLmSOjo66IknnqDq6urDOi9wD/pad9ZZZ9HIyAiNj4/TI488QkRE//RP/8RivvzlLxMR0cMPP8z6Ozs72RpNRJO/zv7ud78T6+whHnroIVqwYAHNnz+freWrVq0iIppcy90E0gT01wUwl8vR3LlzxX/r6emZHKDbt28npZQxjojI5/NNxhERXX311e94zlQqxRbOzs7Ow75+AN7OkYylXbt2ERHR4sWLpxR/8cUXU2NjIz322GMUjUYP+7zAPeibzEPr4NjYGPX395Nt29Td3c1impqaKJFIUH9/P+s3jfVPfvKT9KMf/Yiuu+46+vrXv06rV6+myy67jC6//HKy7b/+/bt9+3bavHnzZGpNZ3Bw8LDvb6aCzcB7wHEcsiyL/vjHP5LH4xH//dBieGg3escdd9CyZcuMx9IXzlAodHQvFrgW01h6J+OsSqVyROf6xCc+Qffffz898MADdMMNNxzRsYA7MK2dRMS0UlM1ejON9VAoROvWraO1a9fSww8/TI8++ij94he/oFWrVtGf/vQn8ng85DgOnXDCCXTnnXcaj9vW1jal83+QwGaAaFIxfegv+rezdevWyf/f1dVFSinq7OykefPmvePxurq6iIioqqqKzjnnnKN/wcD1vFdXzEN/felCQv0vrTlz5hDRX3/+nwp33HEHeb1e+tznPkexWIw+9alPvafrAuDttLe3k+M4tH37dlqwYMFk/8DAACWTSWpvb5/ScWzbptWrV9Pq1avpzjvvpO9+97t000030dq1a+mcc86hrq4ueuONN2j16tVwmP2/QDNAf92pnnfeefTb3/6Wdu/ePdm/efNmeuyxxybbl112GXk8Hrr11luF4l8pRSMjI0REtHz5curq6qJ//dd/pYmJCXG+oaGhY3QnwC1EIhEikl/u70R7ezt5PB5at24d6//BD37A2vX19bRixQr68Y9/zOYCERn/lYtlWXTPPffQ5ZdfTldffTX993//93u4CwA4F1xwARERff/732f9h/6Cv/DCC9/1GKOjo6Lv0C+0h/7Z4Jo1a2jfvn30X//1XyI2l8u50usFvwz8X2699VZ69NFH6ayzzqLPfe5zVC6X6e6776ZFixbRm2++SUR//Yv/X/7lX+gb3/gG9fX10SWXXEKxWIx6e3vpN7/5DV1//fX0la98hWzbph/96Ed0/vnn06JFi+iaa66hlpYW2rdvH61du5aqqqro97///TTfMZjJLF++nIiIbrrpJrry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",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "data_iter = next(dataset_train.create_dict_iterator())\n",
    "\n",
    "images = data_iter[\"image\"].asnumpy()\n",
    "labels = data_iter[\"label\"].asnumpy()\n",
    "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n",
    "\n",
    "# 训练数据集中，前六张图片所对应的标签\n",
    "print(f\"Labels: {labels[:6]}\")\n",
    "\n",
    "classes = []\n",
    "\n",
    "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "    for line in f:\n",
    "        line = line.rstrip()\n",
    "        if line:\n",
    "            classes.append(line)\n",
    "\n",
    "# 训练数据集的前六张图片\n",
    "plt.figure()\n",
    "for i in range(6):\n",
    "    plt.subplot(2, 3, i + 1)\n",
    "    image_trans = np.transpose(images[i], (1, 2, 0))\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "    std = np.array([0.2023, 0.1994, 0.2010])\n",
    "    image_trans = std * image_trans + mean\n",
    "    image_trans = np.clip(image_trans, 0, 1)\n",
    "    plt.title(f\"{classes[labels[i]]}\")\n",
    "    plt.imshow(image_trans)\n",
    "    plt.axis(\"off\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "689370e1-448d-488a-881d-20cd8f968f4e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Type, Union, List, Optional\n",
    "import mindspore.nn as nn\n",
    "from mindspore.common.initializer import Normal\n",
    "\n",
    "# 初始化卷积层与BatchNorm的参数\n",
    "weight_init = Normal(mean=0, sigma=0.02)\n",
    "gamma_init = Normal(mean=1, sigma=0.02)\n",
    "\n",
    "class ResidualBlockBase(nn.Cell):\n",
    "    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, norm: Optional[nn.Cell] = None,\n",
    "                 down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlockBase, self).__init__()\n",
    "        if not norm:\n",
    "            self.norm = nn.BatchNorm2d(out_channel)\n",
    "        else:\n",
    "            self.norm = norm\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.conv2 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, weight_init=weight_init)\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "        \"\"\"ResidualBlockBase construct.\"\"\"\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4011ec8d-a21b-4660-b8d1-0c97705a7f51",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResidualBlock(nn.Cell):\n",
    "    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlock, self).__init__()\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm1 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv2 = nn.Conv2d(out_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.norm2 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：1*1卷积层\n",
    "        out = self.norm1(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm2(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv3(out)  # 主分支第三层：1*1卷积层\n",
    "        out = self.norm3(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c67639dd-4c72-4532-aabb-fe860269469f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "               channel: int, block_nums: int, stride: int = 1):\n",
    "    down_sample = None  # shortcuts分支\n",
    "\n",
    "    if stride != 1 or last_out_channel != channel * block.expansion:\n",
    "\n",
    "        down_sample = nn.SequentialCell([\n",
    "            nn.Conv2d(last_out_channel, channel * block.expansion,\n",
    "                      kernel_size=1, stride=stride, weight_init=weight_init),\n",
    "            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n",
    "        ])\n",
    "\n",
    "    layers = []\n",
    "    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n",
    "\n",
    "    in_channel = channel * block.expansion\n",
    "    # 堆叠残差网络\n",
    "    for _ in range(1, block_nums):\n",
    "\n",
    "        layers.append(block(in_channel, channel))\n",
    "\n",
    "    return nn.SequentialCell(layers)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "24ef916c-da90-46ba-a6d2-c005a64de940",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from mindspore import load_checkpoint, load_param_into_net\n",
    "\n",
    "\n",
    "class ResNet(nn.Cell):\n",
    "    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n",
    "        super(ResNet, self).__init__()\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        # 第一个卷积层，输入channel为3（彩色图像），输出channel为64\n",
    "        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)\n",
    "        self.norm = nn.BatchNorm2d(64)\n",
    "        # 最大池化层，缩小图片的尺寸\n",
    "        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n",
    "        # 各个残差网络结构块定义\n",
    "        self.layer1 = make_layer(64, block, 64, layer_nums[0])\n",
    "        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n",
    "        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n",
    "        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n",
    "        # 平均池化层\n",
    "        self.avg_pool = nn.AvgPool2d()\n",
    "        # flattern层\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 全连接层\n",
    "        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        x = self.conv1(x)\n",
    "        x = self.norm(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.max_pool(x)\n",
    "\n",
    "        x = self.layer1(x)\n",
    "        x = self.layer2(x)\n",
    "        x = self.layer3(x)\n",
    "        x = self.layer4(x)\n",
    "\n",
    "        x = self.avg_pool(x)\n",
    "        x = self.flatten(x)\n",
    "        x = self.fc(x)\n",
    "\n",
    "        return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "0bf1cbe3-ff44-45a6-ac0f-a5bec907176a",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n",
    "            input_channel: int):\n",
    "    model = ResNet(block, layers, num_classes, input_channel)\n",
    "\n",
    "    if pretrained:\n",
    "        # 加载预训练模型\n",
    "        download(url=model_url, path=pretrained_ckpt, replace=True)\n",
    "        param_dict = load_checkpoint(pretrained_ckpt)\n",
    "        load_param_into_net(model, param_dict)\n",
    "\n",
    "    return model\n",
    "\n",
    "\n",
    "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n",
    "    \"\"\"ResNet50模型\"\"\"\n",
    "    resnet50_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n",
    "    resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n",
    "    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n",
    "                   pretrained, resnet50_ckpt, 2048)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0e89acd2-be51-430d-93e5-91b918f8e4e3",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt (97.7 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 102M/102M [00:00<00:00, 313MB/s]\n",
      "Successfully downloaded file to ./LoadPretrainedModel/resnet50_224_new.ckpt\n"
     ]
    }
   ],
   "source": [
    "# 定义ResNet50网络\n",
    "network = resnet50(pretrained=True)\n",
    "\n",
    "# 全连接层输入层的大小\n",
    "in_channel = network.fc.in_channels\n",
    "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n",
    "# 重置全连接层\n",
    "network.fc = fc\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f73aacae-175e-4e23-8078-08f3b08d23d3",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 设置学习率\n",
    "num_epochs = 5\n",
    "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n",
    "                        step_per_epoch=step_size_train, decay_epoch=num_epochs)\n",
    "# 定义优化器和损失函数\n",
    "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n",
    "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n",
    "\n",
    "\n",
    "def forward_fn(inputs, targets):\n",
    "    logits = network(inputs)\n",
    "    loss = loss_fn(logits, targets)\n",
    "    return loss\n",
    "\n",
    "\n",
    "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n",
    "\n",
    "\n",
    "def train_step(inputs, targets):\n",
    "    loss, grads = grad_fn(inputs, targets)\n",
    "    opt(grads)\n",
    "    return loss\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "b881f011-689d-4b5a-ba9e-c646254c9bc4",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# 创建迭代器\n",
    "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n",
    "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n",
    "\n",
    "# 最佳模型存储路径\n",
    "best_acc = 0\n",
    "best_ckpt_dir = \"./BestCheckpoint\"\n",
    "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"\n",
    "\n",
    "if not os.path.exists(best_ckpt_dir):\n",
    "    os.mkdir(best_ckpt_dir)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "01483497-3a55-452c-be4c-96213a8e9ff6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore.ops as ops\n",
    "\n",
    "\n",
    "def train(data_loader, epoch):\n",
    "    \"\"\"模型训练\"\"\"\n",
    "    losses = []\n",
    "    network.set_train(True)\n",
    "\n",
    "    for i, (images, labels) in enumerate(data_loader):\n",
    "        loss = train_step(images, labels)\n",
    "        if i % 100 == 0 or i == step_size_train - 1:\n",
    "            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n",
    "                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n",
    "        losses.append(loss)\n",
    "\n",
    "    return sum(losses) / len(losses)\n",
    "\n",
    "\n",
    "def evaluate(data_loader):\n",
    "    \"\"\"模型验证\"\"\"\n",
    "    network.set_train(False)\n",
    "\n",
    "    correct_num = 0.0  # 预测正确个数\n",
    "    total_num = 0.0  # 预测总数\n",
    "\n",
    "    for images, labels in data_loader:\n",
    "        logits = network(images)\n",
    "        pred = logits.argmax(axis=1)  # 预测结果\n",
    "        correct = ops.equal(pred, labels).reshape((-1, ))\n",
    "        correct_num += correct.sum().asnumpy()\n",
    "        total_num += correct.shape[0]\n",
    "\n",
    "    acc = correct_num / total_num  # 准确率\n",
    "\n",
    "    return acc\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4aaf971a-4f8c-472e-9b9d-843c5ab67e4a",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start Training Loop ...\n",
      "Epoch: [  1/  5], Steps: [  1/196], Train Loss: [2.425]\n",
      "Epoch: [  1/  5], Steps: [101/196], Train Loss: [1.419]\n",
      "Epoch: [  1/  5], Steps: [196/196], Train Loss: [1.096]\n",
      "--------------------------------------------------\n",
      "Epoch: [  1/  5], Average Train Loss: [1.622], Accuracy: [0.597]\n",
      "--------------------------------------------------\n",
      "Epoch: [  2/  5], Steps: [  1/196], Train Loss: [1.115]\n",
      "Epoch: [  2/  5], Steps: [101/196], Train Loss: [1.015]\n",
      "Epoch: [  2/  5], Steps: [196/196], Train Loss: [1.032]\n",
      "--------------------------------------------------\n",
      "Epoch: [  2/  5], Average Train Loss: [1.009], Accuracy: [0.680]\n",
      "--------------------------------------------------\n",
      "Epoch: [  3/  5], Steps: [  1/196], Train Loss: [0.940]\n",
      "Epoch: [  3/  5], Steps: [101/196], Train Loss: [0.749]\n",
      "Epoch: [  3/  5], Steps: [196/196], Train Loss: [0.882]\n",
      "--------------------------------------------------\n",
      "Epoch: [  3/  5], Average Train Loss: [0.845], Accuracy: [0.721]\n",
      "--------------------------------------------------\n",
      "Epoch: [  4/  5], Steps: [  1/196], Train Loss: [0.785]\n",
      "Epoch: [  4/  5], Steps: [101/196], Train Loss: [0.853]\n",
      "Epoch: [  4/  5], Steps: [196/196], Train Loss: [0.810]\n",
      "--------------------------------------------------\n",
      "Epoch: [  4/  5], Average Train Loss: [0.769], Accuracy: [0.739]\n",
      "--------------------------------------------------\n",
      "Epoch: [  5/  5], Steps: [  1/196], Train Loss: [0.654]\n",
      "Epoch: [  5/  5], Steps: [101/196], Train Loss: [0.707]\n"
     ]
    }
   ],
   "source": [
    "# 开始循环训练\n",
    "print(\"Start Training Loop ...\")\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    curr_loss = train(data_loader_train, epoch)\n",
    "    curr_acc = evaluate(data_loader_val)\n",
    "\n",
    "    print(\"-\" * 50)\n",
    "    print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n",
    "        epoch+1, num_epochs, curr_loss, curr_acc\n",
    "    ))\n",
    "    print(\"-\" * 50)\n",
    "\n",
    "    # 保存当前预测准确率最高的模型\n",
    "    if curr_acc > best_acc:\n",
    "        best_acc = curr_acc\n",
    "        ms.save_checkpoint(network, best_ckpt_path)\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n",
    "      f\"save the best ckpt file in {best_ckpt_path}\", flush=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e436763-9657-4228-b323-1f3a93ec847e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "def visualize_model(best_ckpt_path, dataset_val):\n",
    "    num_class = 10\n",
    "    net = resnet50(num_class)\n",
    "    # 加载模型参数\n",
    "    param_dict = ms.load_checkpoint(best_ckpt_path)\n",
    "    ms.load_param_into_net(net, param_dict)\n",
    "    # 加载验证集的数据进行验证\n",
    "    data = next(dataset_val.create_dict_iterator())\n",
    "    images = data[\"image\"]\n",
    "    labels = data[\"label\"]\n",
    "    # 预测图像类别\n",
    "    output = net(data['image'])\n",
    "    pred = np.argmax(output.asnumpy(), axis=1)\n",
    "\n",
    "    # 图像分类\n",
    "    classes = []\n",
    "\n",
    "    with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "        for line in f:\n",
    "            line = line.rstrip()\n",
    "            if line:\n",
    "                classes.append(line)\n",
    "\n",
    "    # 显示图像及图像的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确，显示为蓝色；若预测错误，显示为红色\n",
    "        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n",
    "        plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n",
    "        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n",
    "        mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "        std = np.array([0.2023, 0.1994, 0.2010])\n",
    "        picture_show = std * picture_show + mean\n",
    "        picture_show = np.clip(picture_show, 0, 1)\n",
    "        plt.imshow(picture_show)\n",
    "        plt.axis('off')\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# 使用测试数据集进行验证\n",
    "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f796f53e-f5a6-4f49-b758-aed0862a2168",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.21"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
